package com.demo.qqserver.service;

import com.demo.qqcommon.Message;
import com.demo.qqcommon.MessageType;
import com.demo.qqcommon.User;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;

/**
 * 这是服务端，在监听 9999端口
 * 等待客户端的连接，并保持通讯
 *
 * @author gy
 * @date 2022/5/5
 */
public class QQServer {
    /**
     * 该 ServerSocket 因为我们要使用，做成属性
     */
    private ServerSocket ss = null;

    /**
     * 创建一个集合用于存储 合法用户，模拟 DB
     * 优化：可以使用 ConcurrentHashMap<>() 能够解决并发存在的线程安全问题
     */
    private static final HashMap<String, User> validUsers = new HashMap<>();

    /**
     * 用于存放离线 消息
     * key：getterId
     * value：离线消息集合
     */
    private static final ConcurrentHashMap<String, ArrayList<Message>> offlineDB = new ConcurrentHashMap<>();

    /**
     * 让 ServerConnectClientThread 可以拿到 offlineDB
     * @return
     */
    public static ConcurrentHashMap<String, ArrayList<Message>> getOfflineDB() {
        return offlineDB;
    }


    static {
        // 在静态代码块中 初始化 validUsers
        validUsers.put("100", new User("100", "123456"));
        validUsers.put("200", new User("200", "123456"));
        validUsers.put("300", new User("300", "123456"));
        validUsers.put("400", new User("400", "123456"));
        validUsers.put("500", new User("500", "123456"));
        validUsers.put("广玉", new User("广玉", "123456"));
        validUsers.put("马武", new User("马武", "123456"));
        validUsers.put("祖钊", new User("祖钊", "123456"));
        validUsers.put("老白", new User("老白", "123456"));
        validUsers.put("宋佳露", new User("宋佳露", "123456"));
        validUsers.put("小陈", new User("小陈", "123456"));
    }

    /**
     * 验证用户是否有效
     */
    public boolean checkUser(String userId, String pwd) {
        User user = validUsers.get(userId);
        // 过关斩将
        if (user == null) {
            // 用户不存在
            return false;
        }
        // 密码错误
        return pwd.equals(user.getPassword());
    }

    /**
     * 发送离线消息给 登录成功的用户
     */
    public void sendOfflineMsgToGetter(String userId) {
        offlineDB.keySet().forEach(getter ->
        {
            // 如果 登录的新用户 和 存放有离线消息的 用户相同，将 离线消息发送给它
            if (userId.equals(getter)) {
                ArrayList<Message> messages = offlineDB.get(getter);
                // 开始将消息逐条发送给该用户
                messages.forEach(msg -> {
                    try {
                        // 判断该条消息是否属于 该接收者，因为 messages 存在了多个 接收者的信息
                        if(msg.getGetter().equals(getter)) {
                            ObjectOutputStream oos = new ObjectOutputStream(ManageClientsThread.
                                    getServerConnectClientThread(getter).getSocket().getOutputStream());
                            oos.writeObject(msg);
                        }
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                });
                System.out.println("已成功将"+ getter +"用户的离线消息推送成功");
                // 然后将 该用户 从 离线消息集合中移除
                offlineDB.remove(getter);
            }
        });
    }

    public QQServer() {
        try {
            System.out.println("服务器端在 9999 端口监听，等待连接");
            // 启动推送 服务器消息 的线程
            new Thread(new SendNewsToAllService()).start();

            // 注意：端口可以写在配置文件中，进行读取
            ss = new ServerSocket(9999);

            // 当和某个客户端连接后，我会继续进行监听，所以使用 while循环
            while (true) {
                Socket socket = ss.accept();
                // 得到socket 关联的对象输入流
                ObjectInputStream ois = new ObjectInputStream(socket.getInputStream());
                // 得到socket 关联的对象输出流
                ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());

                User u = (User) ois.readObject();

                // 创建 Message 对象，准备回复客户端
                Message msg = new Message();

                if (checkUser(u.getUserId(), u.getPassword())) {
                    // 登录通过
                    msg.setMsgType(MessageType.MESSAGE_LOGIN_SUCCESS);
                    oos.writeObject(msg);
                    // 创建一个线程 和客户端保持通讯，该线程同样需要持有socket 对象
                    ServerConnectClientThread scct = new ServerConnectClientThread(socket, u.getUserId());
                    // 启动线程
                    scct.start();
                    // 将该线程放入到集合中进行管理
                    ManageClientsThread.addServerConnectClientThread(u.getUserId(), scct);

                    // 用户登录成功后 开始判断是否有离线消息
                    sendOfflineMsgToGetter(u.getUserId());

                } else {
                    // 登录失败
                    System.out.println("用户名 " + u.getUserId() + "密码 " + u.getPassword() + " 登录失败");
                    msg.setMsgType(MessageType.MESSAGE_LOGIN_FAIL);
                    oos.writeObject(msg);
                    // 关闭socket
                    socket.close();

                }

            }

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            // 如果服务器退出了 while，说明服务器端不再监听，因此关闭ServerSocket
            try {
                ss.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

    }
}
